import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.sql.Timestamp;

public class Node {
        private int Lid = 0;
        private int Rid = 0;
        private int Did = 0;
        private InetAddress Lip;
        private InetAddress Rip;
        private InetAddress Dip;
        private int id;
        private InetAddress ip;
        private DatagramSocket socket;
        private InetAddress broadcast;
        private int port;
        private int portACK;
        private boolean imRoot;
    	private int weight=0;
    	private int LWeight=0;
    	private int RWeight=0;
        private int leafPortLeft;
        private int leafPortRight;
       
    	private int portPIn=0;
    	private int portLIn;
    	private int portRIn;
    	private int portPOut;
    	private int portLOut;
    	private int portROut;
    	private GateIn Din;
    	private GateIn Lin;
    	private GateIn Rin;
    	public GateOut DOut;
    	public GateOut LOut;
    	public GateOut ROut;
    	public Deliver D;
    	
    	int[] acked;
        public Node(DatagramSocket socket, int txPort, int portACK,int leafPortLeft, int leafPortRight, String broadcast, int id) throws Exception{
                Lip=InetAddress.getByName("0.0.0.0");
                Rip=InetAddress.getByName("0.0.0.0");
                Dip=InetAddress.getByName("0.0.0.0");
                this.socket=socket;
                this.broadcast=InetAddress.getByName(broadcast);
                port=txPort;
                this.leafPortLeft=leafPortLeft;
                this.leafPortRight=leafPortRight;
                this.portACK=portACK;
                this.id=id;
                portPIn = 9007;
                portLIn=9005;
        		portRIn=9006;
        		new File(""+id).mkdir();
        		new File(""+id+"/mine").mkdir();
        		File deliver=new File(""+id+"/deliver");
        		if (deliver.exists())
        			deliver.delete();
        		deliver.mkdir();
        		deliver.deleteOnExit();
        		D = new Deliver(this);
        		Din = new GateIn(this, 1, portPIn);
        		Lin = new GateIn(this, 2, portLIn);
        		Rin = new GateIn(this, 3, portRIn);
    			Rin.start();
    			Lin.start();
    			Din.start();
        		D.start();
        		acked = new int[255];
        		for (int i=0;i<255;i++){
            		acked[i]=0;
            	}
        		
        }
       
    	/**
    	 * Write an IP address to an array
    	 * @param array packet array
    	 * @param position start position. Where to start inserting the 4-byte IP address
    	 * @param ip IP address to insert in the array
    	 * @return
    	 */
    	private byte[] fillAddress(byte[] array, int position, InetAddress ip){
    		byte[] indirizzo= new byte[4];
    		indirizzo= ip.getAddress();
    		array[position]=indirizzo[0];
    		array[position+1]=indirizzo[1];
    		array[position+2]=indirizzo[2];
    		array[position+3]=indirizzo[3];
    		return array;
    	}
    	
    	/**
    	 * Send a broadcast DSCVRY packet to find the ROOT node
    	 * @throws Exception
    	 */
        public void sendDSCVRY() throws IOException{
				byte[] sendData = new byte[7];
                sendData[0] = (byte) 0;
                sendData[4] = (byte) id;
                DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, broadcast, port);
                try{
                socket.send(sendPacket);
                }catch(java.io.IOException e){this.setIsRoot(true);};
        }
       
        /**
    	 * Send a IMROOT packet to the node which is looking for the root. Used only by the ROOT node.
    	 * @param ip IP address of the node which is looking for ROOT
    	 * @throws Exception
    	 */
        public void sendIMROOT(InetAddress ip, int id)throws Exception{
                byte[] sendData = new byte[7];
                sendData[0] = 1;
                sendData[4] = (byte) this.id;
                DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, ip, port);
                socket.send(sendPacket);
                System.out.println("[R-Reply] IMROOT message sent to "+id);
        }
         
    	/**
    	 * Send a PLCRQ packet to <code>ip</code> (usually root) to ask for this node position in the binary tree.
    	 * @param ip IP field of the root node
    	 * @throws IOException
    	 */
        public void sendPLCRQ(InetAddress ip)throws IOException{
    		byte[] sendData = new byte[20];
    		sendData[0]=2;
    		sendData[4]=(byte) id;
    		sendData=fillAddress(sendData,7,this.ip);
    		DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length,ip, port);
    		socket.send(sendPacket);
    		System.out.println("[Configuration] PLCRQ message sent to "+ip.getHostAddress());
    	}
        
        public void sendCLOSE(int receiverID) throws IOException{
            byte[] sendData = new byte[8];
            byte[] indirizzo = new byte[4];

            sendData[0]=4;
            sendData[4]=(byte)id;
            sendData[7]=(byte)receiverID;
     		forward(sendData);
            System.out.println("[UDP]Sent CLOSE ");
    }
        
        //Send packetON ------|--6--|---ID--|--IP a.b.c.d--|------
         public void sendNodeOn(/*InetAddress ip,*/ int idRx)throws IOException{
                byte[] sendData = new byte[20];
                byte[] indirizzo = new byte[4];

                sendData[0]=6;
                sendData[4]=(byte)id;
                /*indirizzo= this.ip.getAddress();
                sendData[7]=indirizzo[0];
                sendData[8]=indirizzo[1];
                sendData[9]=indirizzo[2];
                sendData[10]=indirizzo[3];
               */
                sendData[7]=(byte)idRx;
                //DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length,ip, port);
                //socket.send(sendPacket);
                forward(sendData);
                //System.out.println("[UDP]Sent NodeOn to ID " + idRx);
        }
         
      /*   public void sendNodeOn(InetAddress ip, int idRx, int who)throws IOException{
             byte[] sendData = new byte[20];
             byte[] indirizzo = new byte[4];

             sendData[0]=6;
             sendData[4]=(byte)id;
             indirizzo= this.ip.getAddress();
             sendData[7]=indirizzo[0];
             sendData[8]=indirizzo[1];
             sendData[9]=indirizzo[2];
             sendData[10]=indirizzo[3];
             //sendData[11]=(byte)who;
             				
			 DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length,ip, port);
				
             socket.send(sendPacket);
             
             System.out.println("[UDP]Sent NodeOn to ID " + idRx);
     }*/
       
         public void sendACKtoPLC(int receiverID)throws IOException{
             byte[] sendData = new byte[20];
             //byte[] indirizzo = new byte[4];

             sendData[0]=7;
             sendData[4]=(byte)id;
             //indirizzo= this.ip.getAddress();
             sendData[7]=(byte)receiverID;
            
             forward(sendData);
             //System.out.println("[UDP]Sent ACK to ID " + idRx);
     }
         
         public void sendACKtx(int receiverID,int n)throws IOException{
             byte[] sendData = new byte[20];
             //byte[] indirizzo = new byte[4];

             sendData[0]=50;
             sendData[4]=(byte)id;
             //indirizzo= this.ip.getAddress();
             sendData[7]=(byte)receiverID;
             sendData[11]=(byte)n;
            
             forward(sendData);
             //System.out.println("[UDP]Sent ACK to ID " + idRx);
     }
         
         
         /**
     	 * Send a ACK packet to <code>ip</code> for proving node availability.
     	 * @param ip IP field of rx node
     	 * @param idRx ID field of rx node
     	 * @param who specifies kind of node checking (0 = ACK_PORT, 1 = ACK_LEFT_PORT, 2 = ACK_RIGHT_PORT)
     	 * @throws IOException
     	 */
          public void sendACK(InetAddress ip, int idRx/*, int who*/)throws IOException{
                 byte[] sendData = new byte[20];
                 //byte[] indirizzo = new byte[4];

                 sendData[0]=7;
                 sendData[4]=(byte)id;
               
                 DatagramPacket sendPacket=null;
                 /*
                 if(who==0){
 					sendPacket = new DatagramPacket(sendData, sendData.length,ip, portACK);
 				 //	System.out.println("[UDP]Sent ACK to ID " + idRx);
 				 }
 				 else if(who==1){
 					sendPacket = new DatagramPacket(sendData, sendData.length,ip, leafPortLeft);
 					//System.out.println("[UDP]Sent ACK by left leaf to ID " + idRx);
 				 }
 				 else{
 					sendPacket = new DatagramPacket(sendData, sendData.length,ip, leafPortRight);
 				 //	System.out.println("[UDP]Sent ACK by right leaf to ID " + idRx);
 				 }
                 */
                 //System.out.println(idRx+" - "+this.Did);
                 if(idRx==this.Did){
                	 if (this.id<this.Did){
                		 sendPacket = new DatagramPacket(sendData, sendData.length,ip,  leafPortLeft);
 //               		 System.out.println("ACK SX to"+this.ip);
                	 }
                	 if (this.id>this.Did){
                		 sendPacket = new DatagramPacket(sendData, sendData.length,ip,  leafPortRight);
//                		 System.out.println("ACK DX");

                	 }
                 }else{
                	
                		 sendPacket = new DatagramPacket(sendData, sendData.length,ip,  portACK);


                	}
                 socket.send(sendPacket);
         }
        
         
         /**
     	 * Send a SETFIELD packet to <code>destId</code>. Allow a node to change a member of other node's family.
     	 * @param destId ID of the destination node
     	 * @param field field to set. 'D':dad ; 'R': right son ; 'L': left son; '*':me (if sent to dad) ;
     	 * @param id ID to set
     	 * @param ip IP to set
     	 * @throws IOException
     	 */
     	public void sendSETFIELD(int destId,char field, int idn,InetAddress ip)throws IOException{
     		byte[] sendData = new byte[20];
     		sendData[0]=8;
     		sendData[4]=(byte)id;
     		sendData[7]=(byte)destId;
     		sendData[10]=(byte)field;
     		sendData[11]=(byte)idn;
     		sendData=fillAddress(sendData,14,ip);
     		System.out.println("[UDP]Sent SETFIELD ");
     		forward(destId,sendData);
     	}

    	public void sendAVLR(int destId, char type)throws IOException{
    		byte[] sendData = new byte[20];
    		sendData[0]=9;
    		sendData[4]=(byte)this.id;
    		sendData[7]=(byte)destId;
    		sendData[10]=(byte)type;
    		System.out.println("[UDP]Sent SETFIELD ");
    		forward(sendData);
    	}
    	
    	
    	public void sendECHORQ(int destId)throws IOException{
    		byte[] sendData = new byte[20];
    		int code = (int)Math.random()*100;
    		sendData[0]=40;
    		sendData[4]=(byte)this.id;
    		sendData[7]=(byte)destId;
    		sendData[10]=(byte) code;
    		
    		forward(sendData);
    		java.util.Date date = new java.util.Date();
    		System.out.println("[UDP]Sent ECHORQ "+new java.sql.Timestamp(date.getTime()));
    	}
    	
    	public void sendECHO(int destId, int code)throws IOException{
    		byte[] sendData = new byte[20];
    		
    		sendData[0]=41;
    		sendData[4]=(byte)this.id;
    		sendData[7]=(byte)destId;
    		sendData[10]=(byte) code;
    		System.out.println("[UDP]Sent ECHO ");
    		forward(sendData);
    	}
     	/**
     	 * Forward a packet to the destination indicated in his receiverID field
     	 * @param packet The packet to send
     	 *
     	 */
     	public void forward (byte[] packet)throws IOException{
     		//Dal punto di vista concettuale è meglio chiamare sempre questa funzione.
     		//Tutti i pacchetti che girano devono avere il seguente formato per i primi campi
     		//  ---------4------------7--------------------
     		//  |   ID   |  sender ID |  receiver ID | .....
     		//  ----------------------------------------
     		//La funzione forward(int,byte[]) viene chiamata direttamente solo dai pacchetti PLCRQ perchè contengono
     		//il campo destinazione nella posizione 4 per limitare la dimensione del pacchetto trasmesso.
     		int destId;
     		destId=packet[7];
     		forward(destId,packet);
     	}
     	
    	/**
    	 * Send a Weight Packet to <code>destId</code>
    	 * @param destId ID of the destination node
    	 * @throws IOException
    	 */
    	public void sendWP(int destId)throws IOException{
    		byte[] sendData = new byte[20];
    		
    		sendData[0]=10;
    		sendData[4]=(byte)id;
    		sendData[7]=(byte)destId;
    		sendData[10]=(byte)weight;
    		
    	//	System.out.println("[UDP]Sent WP W: "+weight);
    		forward(sendData);
    	}
     	/**
    	 * Forward a packet to the destination indicated in his receiverID field
    	 * USE DIRECTLY ONLY FOR PLCRQ PACKETS
    	 * @param destId ID of the destination node
    	 * @param packet The packet to send
    	 *
    	 */
    	public void forward(int destId, byte[] packet)throws IOException{
    		InetAddress sip;
    		boolean LS;
    		if(this.id<this.Did){
    			//I'm a left son
    			LS=true;
    		}else
    			LS=false;
    		if(destId>this.id && ((destId<Did && LS==true)||(destId>Did && LS==false) || Did==0) && Rid!=0){
    			//send to right node
    			sip=Rip;
    			DatagramPacket sendPacket = new DatagramPacket(packet, packet.length, sip, port);
    			socket.send(sendPacket);
    //			System.out.println("\u001B[33m [UDP-RForward] Sent PCK to ID "+destId+"\u001B[0m");
    		}
    		if(destId<this.id && ((destId<Did && LS==true) || (destId>Did && LS==false) || Did==0) && Lid!=0){
    			//send to left node
    			sip=Lip;
    			DatagramPacket sendPacket = new DatagramPacket(packet, packet.length, sip, port);
    			socket.send(sendPacket);
   // 			System.out.println("\u001B[33m [UDP-LForward] Sent PCK to ID "+destId+"\u001B[0m");
    		}
    		//CHECK
    		if((destId>=Did && LS==true)||(destId<=Did && LS==false) || (destId<=Did && destId<=id && Lid==0 ) || (destId>=Did && destId>=id && Rid==0 )&& Did!=0){    			//send to dad node
    			sip=Dip;
    			DatagramPacket sendPacket = new DatagramPacket(packet, packet.length, sip, port);
    			socket.send(sendPacket);
    //			System.out.println("\u001B[33m [UDP-DForward] Sent PCK to ID "+destId+"\u001B[0m");
    		}
    	}
       
        //Set function for this node
       
        public void setIsRoot(boolean value){
                imRoot=value;
        }
       
        public void setId(int id){
                this.id=id;
        }
        public void setNode(int id, String ip)throws Exception{
                this.id=id;
                this.ip=InetAddress.getByName(ip);
        }
        public void setNode(int id, InetAddress ip)throws Exception{
                this.id=id;
                this.ip=ip;
        }
        public void setIp(InetAddress ip)throws Exception{
                this.ip=ip;
        }
        public void setIp(String ip)throws Exception{
                this.ip=InetAddress.getByName(ip);
        }
       
        //Set functions for left node
        public void setLeftId(int id){
                Lid=id;
        }
        public void setLeft(int id, String ip)throws Exception{
        	int oldId=Lid;
        	Lid=id;
        	Lip=InetAddress.getByName(ip);
        	if(id!=0 && oldId!=id){
        		if (socketLeft!=null){
        			socketLeft.close();
        		//	System.out.println("L CHIUSO");
        		}
        		socketLeft=new Socket(Lip, (9007));
        		if(LOut==null){
        			LOut = new GateOut(this, socketLeft);
        			LOut.start();
        		}else
        			LOut.setSocket(socketLeft);
        	//	System.out.println("LEFT connesso a :"+socketLeft.getRemoteSocketAddress()+" : "+socketLeft.getPort());
        	if(LWeight==0)
        		LWeight=1;
        	
        	}
        }
        public void setLeft(int id, InetAddress ip)throws Exception{
        		int oldId=Lid;
                Lid=id;
                Lip=ip;
                if(id!=0 && oldId!=id){
                	if (socketLeft!=null){
                		socketLeft.close();
                //		System.out.println("L CHIUSO");
                	}

                	socketLeft=new Socket(Lip, (9007));
                    if(LOut==null){
                    	LOut = new GateOut(this, socketLeft);
                    	LOut.start();
                    }else
                    	LOut.setSocket(socketLeft);
             //       System.out.println("LEFT connesso a :"+socketLeft.getRemoteSocketAddress()+" : "+socketLeft.getPort());
                    if(LWeight==0)
                		LWeight=1;
                }
        }
        public void setLeftIp(InetAddress ip)throws Exception{
                Lip=ip;
        }
        public void setLeftIp(String ip)throws Exception{
                Lip=InetAddress.getByName(ip);
        }
        
        public void setPortLIn(int portLIn) {
    		this.portLIn = portLIn;
    	}
        
        public void setPortLOut(int portLOut) {
    		this.portLOut = portLOut;
    	}
        
       
        //Set functions for right node
        public void setRightId(int id){
                Rid=id;
        }
        public void setRight(int id, String ip)throws Exception{
        		int oldId=Rid;
                Rid=id;
                Rip=InetAddress.getByName(ip);
                if(id!=0 && id!=oldId){
                	
                	socketRight=new Socket(Rip, (9007));
                    if(ROut==null){
                    	ROut = new GateOut(this, socketRight);
                    	ROut.start();
                    }else
                    	ROut.setSocket(socketRight);
               //     System.out.println("RIGHT connesso a :"+socketRight.getRemoteSocketAddress()+" : "+socketRight.getPort());
                    if(RWeight==0)
                		RWeight=1;
                }
        }
        public void setRight(int id, InetAddress ip)throws Exception{
        		int oldId=Rid;
                Rid=id;
                Rip=ip;
                if (id!=0 && id!=oldId){
                	//if (socketRight!=null){
                	//	socketRight.close();
                	//	System.out.println("R CHIUSO");
                	//}
                	socketRight=new Socket(Rip, (9007));
                    if(ROut==null){
                    	ROut = new GateOut(this, socketRight);
                    	ROut.start();
                    }else
                    	ROut.setSocket(socketRight);
                  //  System.out.println("RIGHT connesso a :"+socketRight.getRemoteSocketAddress()+" : "+socketRight.getPort());
               
                if(RWeight==0)
            		RWeight=1;
                }
        }
        public void setRightIp(InetAddress ip)throws Exception{
                Rip=ip;
        }
        public void setRightIp(String ip)throws Exception{
                Rip=InetAddress.getByName(ip);
        }
        
        public void setPortRIn(int portRIn) {
    		this.portRIn = portRIn;
    	}
        public void setPortROut(int portROut) {
    		this.portROut = portROut;
    	}
       
        //Set functions for dad node
        public void setDadId(int id){
                Did=id;
        }
        public void setDad(int id, String ip)throws Exception{
        	int oldId=Did;
        	Did=id;
        	Dip=InetAddress.getByName(ip);
        	if(id!=0 && oldId!=id){
        		if (this.id<Did)

        			socketDad= new Socket (Dip, (9005));
        		else
        			socketDad= new Socket (Dip, (9006));

        		if(DOut==null){
        			DOut = new GateOut(this, socketDad);
        			DOut.start();
        		}else
        			DOut.setSocket(socketDad);
        	//	System.out.println("DAD connesso a :"+socketDad.getRemoteSocketAddress()+" : "+socketDad.getPort());
          //  	System.out.println("SD "+socketDad);
        	}
        	

        }
        public void setDad(int id, InetAddress ip)throws Exception{
        	int oldId=Did;
        	Did=id;
        	Dip=ip;
        	if(id!=0 && id!=oldId){
        		if (this.id<Did)
        			socketDad= new Socket (Dip, (9005));
        		else
        			socketDad= new Socket (Dip, (9006));
        		if(DOut==null){
        			DOut = new GateOut(this, socketDad);
        			DOut.start();
        		}else
        			DOut.setSocket(socketDad);
        	//	System.out.println("DAD connesso a :"+socketDad.getRemoteSocketAddress()+" : "+socketDad.getPort()); 
           // 	System.out.println("SD "+socketDad);
        	}
        }
            	
        public void setDadIp(InetAddress ip)throws Exception{
                Dip=ip;
        }
        public void setDadIp(String ip)throws Exception{
                Dip=InetAddress.getByName(ip);
        }

    	public void deleteRight() throws IOException{
    	if(Rid!=0){
    		if (socketRight!=null){
    			if(!socketRight.isClosed())
    				socketRight.getOutputStream().write(-2);
        		socketRight.close();
        	//	System.out.println("R CHIUSO");
        	}
    		Rip=InetAddress.getByName("0.0.0.0");
    		Rid=0;
    	}
    	}
    	public void deleteLeft() throws IOException{
    		if(Lid!=0){
    		if (socketLeft!=null){
    			if(!socketLeft.isClosed())
    				socketLeft.getOutputStream().write(-2);
        		socketLeft.close();
        	//	System.out.println("L CHIUSO");
        	}
    		Lip=InetAddress.getByName("0.0.0.0");
    		Lid=0;
    		}
    	}
    	public void deleteDad() throws IOException{
    		if(Did!=0){
    		if (socketDad!=null){
    			if(!socketDad.isConnected())
    				socketDad.getOutputStream().write(-2);
        		socketDad.close();
        	//	System.out.println("D CHIUSO");
        	}
    		Dip=InetAddress.getByName("0.0.0.0");
    		Did=0;
    		
    		}
    	}
    	
    	public void setLeftWeight(int w,char flooder){
    		LWeight=Math.abs(w);
    //TOLTO, SEMBRA FUNZIONARE COSÌ		
    //		if(RWeight==LWeight)
    //			weight=0;
    //		else
    			if(LWeight>RWeight)
    				weight=LWeight;
    			else
    				weight=RWeight;
    		boolean LS;
    		if (id<Did)
    			LS=true;
    		else
    			LS=false;
    		if (((LS==true && flooder=='R') ||(LS==false && flooder=='L'))&& imRoot==false)
    			weight=weight*-1;
    		try {
				this.sendWP(this.id);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
    	}
    	
    	public void setRightWeight(int w,char flooder){
    		RWeight=Math.abs(w);
    //TOLTO, SEMBRA FUNZIONARE COSÌ		
    //		if(RWeight==LWeight)
    //			weight=0;
    //		else
    			if(LWeight>RWeight)
    				weight=LWeight;
    			else
    				weight=RWeight;
    		boolean LS;
    		if (id<Did)
    			LS=true;
    		else
    			LS=false;
    		if (((LS==true && flooder=='R') || (LS==false && flooder=='L'))&& imRoot==false)
    			weight=weight*-1;
    		try {
				this.sendWP(this.id);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
    	}
    	
    	public void setPortPIn(int portPIn) {
    		this.portPIn = portPIn;
    	}
    	
    	public void setPortPOut(int portPOut) {
    		this.portPOut = portPOut;
    	}
    	
        //Funtions used to get values
        public boolean isRoot(){
                return imRoot;
        }
        public int getDadId(){
                return Did;
        }
        public InetAddress getDadIp(){
                return Dip;
        }
        public int getLeftId(){
                return Lid;
        }
        public InetAddress getLeftIp(){
                return Lip;
        }
        public int getRightId(){
                return Rid;
        }
        public InetAddress getRightIp(){
                return Rip;
        }
        public int getId(){
                return id;
        }
        public InetAddress getIp(){
                return ip;
        }
       
    	public int getWeight(){
    		return weight;
    	}
    	public int getLeftWeight(){
    		return LWeight;
    	}
    	public int getRightWeight(){
    		return RWeight;
    	}
    	
    	public int getPortPIn() {
    		return portPIn;
    	}

    	public int getPortLIn() {
    		return portLIn;
    	}

    	public int getPortRIn() {
    		return portRIn;
    	}

    	public int getPortPOut() {
    		return portPOut;
    	}

    	public int getPortLOut() {
    		return portLOut;
    	}

    	public int getPortROut() {
    		return portROut;
    	}
    	
        //Print status functions
        public void printState(){
                System.out.println("[STATUS] D: "+Did+", L: "+Lid+", R: "+Rid+"");
                System.out.println("[STATUS] D: "+Did+"("+Dip.getHostAddress()+") L: "+Lid+"("+Lip.getHostAddress()+") R: "+Rid+"("+Rip.getHostAddress()+")");

        }
        /**
    	 * Print the node's family in text-"graphical" view
    	 */
    	public void printGraph(){
    		System.out.println("            "+Did+" ("+Dip.getHostAddress()+")");
    		System.out.println("                    |");
    		if(imRoot==true)
    			System.out.println("         \u001B[31m *\u001B[32m[ "+id+" ("+ip.getHostAddress()+") {"+weight+" ("+LWeight+"/"+RWeight+")} ]\u001B[0m");
    		else
    			System.out.println("          \u001B[32m[ "+id+" ("+ip.getHostAddress()+") {"+weight+" ("+LWeight+"/"+RWeight+")} ]\u001B[0m");
    		System.out.println("   |----------------|-------------|");
    		System.out.println(" "+Lid+" ("+Lip.getHostAddress()+")               "+Rid+" ("+Rip.getHostAddress()+")");
    		
    	}
    	
    	static Socket socketDad;
    	static Socket socketLeft;
    	static Socket socketRight;
    	
    	public void determineNextHop(int destId, byte[] buffer,int i) throws IOException
    	{
    		boolean LS;    		
    		if(this.id<this.Did){
    			//I'm a left son
    			LS=true;
    		}else
    			LS=false;
    		boolean b=false;
    		boolean b2=false;
    		do{
    		if(destId>this.id && ((destId<Did && LS==true)||(destId>Did && LS==false) || Did==0) && Rid!=0){
    			//send to right node
    		//	System.out.println("R");
    		//	System.out.println(ROut);
    			if(ROut!=null){
    			ROut.setSocket(socketRight);
    			b=ROut.connected;
    			if(b)
    				ROut.putBuf(buffer);
    			b2=ROut.connected;
    			}
    		}
    		if(destId<this.id && ((destId<Did && LS==true) || (destId>Did && LS==false) || Did==0) && Lid!=0){
    			//send to left node
    		//	System.out.println("L");
    		//	System.out.println(LOut);
    			if(LOut!=null){
    			LOut.setSocket(socketLeft);
    			b=LOut.connected;
    			if (b)
    				LOut.putBuf(buffer);
    			b2=LOut.connected;
    			}
    		}

    		//CHECK
    		if((destId>=Did && LS==true)||(destId<=Did && LS==false) || (destId<=Did && destId<=id && Lid==0 ) || (destId>=Did && destId>=id && Rid==0 )&& Did!=0){
    			//send to dad node
    		//	System.out.println("D");
    			if(DOut!=null){
    			//System.out.println(DOut);
    			DOut.setSocket(socketDad);
    			b=DOut.connected;
    			if(b)
    				DOut.putBuf(buffer);    			
    			b2=DOut.connected;
    		}}
    		}while(!b || b2!=b);
    		buffer=new byte[1024];
    	}

    	public void startGateInD()
    	{
    	//	System.out.println("creo DOUT");
 			DOut = new GateOut(this, socketDad);
    		//System.out.println("DAD connesso a :"+socketDad.getRemoteSocketAddress()+" : "+socketDad.getPort());
    		DOut.start();
    		
    	}
    	public void startGateInL()
    	{
    	//	System.out.println("creo LOUT");
    		LOut = new GateOut(this, socketLeft);
    //		System.out.println("LEFT connesso a :"+socketLeft.getRemoteSocketAddress()+" : "+socketLeft.getPort());
    		LOut.start();
    	}
    	public void startGateInR()
    	{
    	//	System.out.println("creo ROUT");
    		ROut = new GateOut(this, socketRight);
		//	System.out.println("RIGHT connesso a :"+socketRight.getRemoteSocketAddress()+" : "+socketRight.getPort());
    		ROut.start();	
    	}
    	
    	
    	
    	
    	int latestR=0;
    	
    	/*************************
    	 * 
    	 * CLASSE GATE-OUT
    	 *
    	 */
    	class GateOut extends Thread
    	{
    		protected boolean deliverySuspended;
    		private Node node;
    		protected byte buff[];
    		private Socket connectionOut;
    		private boolean bufAval;
    		private boolean msg_disp;
    		private boolean close=false;
    		public boolean connected=false;
    		DataOutputStream output;
    		boolean resend=false;
    		boolean unstop=true;
    		public GateOut(Node n, Socket s)
    		{
    			deliverySuspended = true;
    			node = n;
    			connectionOut = s;
    			bufAval = true;
    			msg_disp=false;
    		}
    		
    		public void setSocket(Socket s) throws IOException{
    			connectionOut=s;
    			//connectionOut.setSendBufferSize(1024);
    			if(connectionOut!=null)
    			if (!connectionOut.isConnected())
    				connected=false;
    			else
    				connected=true;
    		}
    		
    		public synchronized void putBuf (byte buffer[])
    		{ 
    			while (!bufAval) //wait
    			try { wait ();} 
    			catch (InterruptedException e){} 
    			bufAval=false; 
    			msg_disp=true;
    			buff = buffer; 
    			notifyAll();
    		}
    		
    		public synchronized void sendBuf()
    		{ 
    			while (!msg_disp) //wait
    			try { wait ();} 
    			catch (InterruptedException e){} 
    			msg_disp = false;
    			bufAval=true;
    			sendPck();
    			notifyAll();
    		}
    		
    		public void ack(int n){
    			//System.out.println("ACK"+n+ "latest "+latestR);
    			if (n==latestR){
    				unstop=true;
    				resend=false;
    			}else{
    				resend=true;
    				unstop=true;
    			}
    			
    		}
    		
    		void closeSocket(){
    			close=true;
    			
    		}
    		public void sendPck(){
        		try{
        			if(close!=true){
        				
        				output = new DataOutputStream(connectionOut.getOutputStream());
	    	    		if (buff!=null){
	    	    	//	do{	
	    	    			resend=false;
	    	    			output.write(buff);
	    	    	//		System.out.println(buff[0]+ "-"+buff[1]+" - "+buff[2]);
	    	    	//		System.out.println("sent, Start to wait for ack "+buff[2]);
	    	    			latestR=buff[2];
	    	    			unstop=false;
	    	    	//		while (!unstop && buff[1]==id){ //wait
	    	    	//			int k=0;
	    	        //			try { while(!unstop && buff[1]==id && k<100 ){ //timeout
	    	        //					wait (1);
	    	        //					k++;
	    	        //					if(k==100){
	    	        //						resend=true;
	    	        //						unstop=true;
	    	        //						}
	    	        //				} 
	    	        //			}
	    	        //			catch (InterruptedException e){} 
	    	    	//		}
	    	    	//	}while(resend==true && buff[1]==id);
	    	    			connected=true;
	    	    		
	    	    		}else
	    	    			connected=false;
	    	    		
	    	            buff=new byte[1024];
    	            }else{
    	            	
        				connectionOut.close();
    	            	//System.out.println("SOCKET CHIUSO");
        			}
        		}
        		
        		catch( IOException e){
        			try {
        				System.out.println("Connection Closed");
        				connected=false;
						this.wait(10000);
					} catch (InterruptedException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
        			//e.printStackTrace();
        		}
        		try {
					wait(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
        	}
    		public void run()
    		{
    			while(true){
	    			sendBuf();
    			}
    		}
    	}
    	
    	
    	/********************
    	 * 
    	 * CLASSE DELIVER
    	 *
    	 */
    	class Deliver extends Thread
    	{
    		protected boolean threadSuspended;
    		private Node node;
    		int latest=0;
    		boolean rcvack=true;
    		boolean esc=false;
    		int tentativi=0;
    		public Deliver(Node n)
    		{
    			threadSuspended = false;
    			node = n;
    		}
    		public void run()
    		{
    			while(true){
    				verifyDelivery();
    				try{
    					this.sleep(3000);  //Intervallo ricerca file nella cartella
    					if (Did==0 && Lid==0 && Rid==0){
    					//	printState();
    						sendDSCVRY();
    						}
    				}
    				catch(InterruptedException e){} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
    			}
    		}
    		
    		public void ack(int n) {
    			
    			if(n==-10){
    				esc=true;
    				rcvack=true;
    			}else{
    				latest=n;
        			rcvack=true;
        			tentativi=0;
    			}
    		}
    		public synchronized void verifyDelivery()
    		{
    			File trov=new File(""+id+"/deliver");
    			String filesName[] = trov.list();
				if(filesName!=null && filesName.length!=0){
					if(filesName[0].startsWith(".")){
						trov.delete();
						filesName=null;
					}
				}
    			if(filesName != null && filesName.length!=0){

    				System.out.println("File to deliver found:"+filesName[0]);
    				
    				try{
    					FileInputStream fis = new FileInputStream(""+id+"/deliver/"+filesName[0]);
    					byte[] buffer = new byte[1018];
    					int destId = Integer.parseInt(filesName[0]);
    					buffer[0]=(byte) (destId);
    					buffer[1]=(byte) node.id;

    					short dim = 0;
    				//	System.out.println("DIMENSIONE FILE"+fis.available());
    					int n_pack=fis.available()/1018;

    					int i=0;
    					int bw=0;
    					int n=0;
    					//java.util.Date date = new java.util.Date();
    		    		//System.out.println("Inizio TX a: "+System.currentTimeMillis());
    					while(fis.available()!=0 && !esc){
    						rcvack=false;
    						
    						
    						//System.out.println( dim);
    						if(latest==i){
        						buffer = new byte[1018];
        						dim= (short)Math.min(1018, fis.available());
    							bw=fis.read(buffer, 0, dim);
    							i++;
    							n++;
    							if(i==128)
    								i=2;
    							
    							}else if(latest==0){
    								fis = new FileInputStream(""+id+"/deliver/"+filesName[0]);
    								dim= (short)Math.min(1018, fis.available());
        							bw=fis.read(buffer, 0, dim);
        							i=1;
        							n=1;
    								
    							}
    						//System.out.println("[[[[BW]]]]]= " +bw);
    						byte[] b2=new byte[bw+6];
    						b2[0]=(byte) (destId);
    						b2[1]=(byte) node.id;
    						if(n==n_pack+1)
    							b2[2]=(byte)-1;
    						else if (i==1)
    							b2[2]=(byte)1;
    						else
    							b2[2]=(byte)i;	
    						b2[3]=(byte)(dim & 0xff);
    						b2[4]=(byte)((dim>>8) & 0xff);
    						for (int i1=5;i1<bw+5;i1++){
    							b2[i1]=buffer[i1-5];
    						}
    						b2[bw+5]=(byte)-2;

    						short dimcalcolata= (short) ((b2[4]<<8)+(b2[3]&0xff));
    						//System.out.println("DIMENSIONE PACCHETTO di pack "+i+" da "+b2[1]+" per "+destId+": "+dimcalcolata);

    						node.determineNextHop(Integer.parseInt(filesName[0]),b2,i);
    					//	System.out.println("Trasferito pacchetto "+i);
    						float perc=Math.round(((n*1.0)/(n_pack)*100));
    						if (perc>100)
    							perc=100;
    						System.out.print(" "+perc+"%               \r");
    						int k=0;
    						while(!rcvack && k<300){
    								try{wait(1);k++;}catch (InterruptedException e){}
    						
    						}
    						if(esc==true){
    							sleep(1000);
    							tentativi++;
    							if(tentativi<20)
    								esc=false;
    							System.out.print("Host doesn't exist.    \r");}
    					//	System.out.println("SONO FUORI");
    					}
    					buffer=new byte[1];
    					System.out.println("\n");
    					//TOLTO DOPO INTRODUZIONE ACK, CONTROLLARE SIA OK
    				//	node.determineNextHop(Integer.parseInt(filesName[0]),null,0);
    					if(!esc)
    						System.out.println("End of transmission.");
    					esc=false;
    					tentativi=0;
    					new File(""+id+"/deliver/"+destId).delete();
    					fis.close();


    				}

    				catch( IOException e){
    					e.printStackTrace();
    				} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
    			}
    		}
    	}
    	
    	
    	/**********************
    	 * 
    	 * CLASSE GATE-IN
    	 *
    	 */
    	
    	
    
    	class GateIn extends Thread
    	{
    		private ServerSocket server;
    		private Socket connectionIn;
    		private DataInputStream input;
    		protected boolean threadSuspended = false;
    		boolean exit=false;
    		public GateIn(Node n, int num, int prt)
    		
    		{
    			try{
    				server = new ServerSocket(prt);	
    			}
    			catch( IOException e){
    				e.printStackTrace();
    			}
    		//	System.out.println("Attivato thread in ascolto su porta: "+ prt);
    		}
    		public void run()
    		{
    			//System.out.println("inizio run..."+acked);
    			while(true)
    			{
    				boolean done = false; 
    				byte n=0;
    				byte n_fw=0;
    				try
    				{
    				//	System.out.println("IN ATTESA su "+server.getLocalPort());
    					connectionIn = server.accept();
    				//	System.out.println("Connessione in ingresso da porta "+ connectionIn.getLocalPort()+" "+connectionIn.getRemoteSocketAddress());
    					synchronized (this) 
    					{
    						while(threadSuspended)
    							wait();
    					}
    					input = new DataInputStream(connectionIn.getInputStream());
    					//while(!done)
    					//{
    						byte  l1,l2;
    						byte mitt=0;
    						byte  dest=0;
    					
    						
    					    String inf= connectionIn.getRemoteSocketAddress().toString();
    						do{
    							//System.out.println("G1");
    				
    						try{
    							//	System.out.println("AVAil: "+input.available());
    							//	if(input.available()!=0){
    									dest=input.readByte();
    							//	}else if (n!=0){
    							//		connectionIn.getOutputStream().write((byte)n);
        						//		System.out.println("Inivato ACK "+n);
        						//		wait(1000);
    							//	}
    									
    	//						}catch(java.io.EOFException e){/*System.out.println("Connessione chiusa dall'altro capo");*/done=true;}

    							//System.out.println("G2"+dest+"  "+inf);

    							if(dest==-2){
    								//input.close();
    								break;
    							}
    							try{
    							mitt=input.readByte();
    							}catch(java.io.EOFException e){/*System.out.println("Connessione chiusa dall'altro capo");*/done=true;}
    							if(dest==id) //received pck is for me
    							{
    								
    								
    								
    							//	if(n!=0){
        						
        							//}
    								
    								
    								

    								byte[] data=new byte[1018];
    								try{
    								n=input.readByte();
    								}catch(Exception e){}
    							//	System.out.println("NUM "+n);
    								
    								File salva = new File (""+id+"/mine/"+mitt);
    								FileOutputStream fos;
    								if(n==1){
    									fos = new FileOutputStream(salva);
    									acked[mitt]=0;
    								}else
    									fos = new FileOutputStream(salva,true);

    								boolean drop=false;
        							//	acked++;
        								if(acked[mitt]==n-1){
        									sendACKtx(mitt, n);
        					//				System.out.println("Inivato ACK "+n);
        									acked[mitt]=n;
        									if(acked[mitt]==127){
        										acked[mitt]=1;
        										
        									}
        						    	}else{
        									sendACKtx(mitt,acked[mitt]);
        					//				System.out.println("Inivato ACK "+(acked[mitt]-1));
        									if(n!=(byte)-1)
        										drop=true;
        							//		acked=acked-1;
        								}
        			//					System.out.println(input.available());
    								
    								
    								
    								
    								
    								
    								//	this.sleep(10);
    								l1=input.readByte();
    								l2=input.readByte();
    								short dim= (short) ((l2<<8)+(l1&0xff));
    					//			System.out.println(mitt+" ->X");

    								int letti=0;
    								do{
    									int k=input.read(data, letti, dim-letti);
    									letti=letti+k;
    								}while(letti<dim);
    				//				System.out.println("OK");
    							//	if (input.read()==-1)
    							//		System.out.print((byte)input.read());
    								if((byte)input.read()==-2 && !drop)
    									fos.write(data, 0, dim);
    								else{
    						//			System.out.println("PACKET DROPPED");
    									drop=false;
    								}
    								data=new byte[1018];
    								fos.flush();
    								fos.close();
    								if(n==(byte)-1){
    			    					//java.util.Date date2 = new java.util.Date();
    			    		    		//System.out.println("Fine RX a: "+	System.currentTimeMillis());
    									System.out.println("File received!");
    									acked[mitt]=0;
    									java.util.Date date = new java.util.Date();
    									File salva_finale = new File (""+id+"/mine/"+mitt+" "+new Timestamp(date.getTime()));
    								
    									salva.renameTo(salva_finale);
    								}
    							}
    							else if(dest!=0)
    							{
    								boolean LS;
    								if(id<Did){
    	            	    			//I'm a left son
    	            	    			LS=true;
    	            	    		}else
    	            	    			LS=false;
    								boolean esc=false;
    								if(dest>id && ((dest<Did && LS==true)||(dest>Did && LS==false) || Did==0)){
    					    			//send to right node
    					    		//	System.out.println("R");

    					    			if (Rid==0){esc=true;}
    					    		}
    					    		if(dest<id && ((dest<Did && LS==true) || (dest>Did && LS==false) || Did==0)){
    					    			//send to left node
    					    		//	System.out.println("L");

    					    			if(Lid==0){esc=true;}
    					    			
    					    		}

    					    		//CHECK
    					    		if((dest>=Did && LS==true && Rid==0)||(dest<=Did && LS==false && Lid==0) || (dest<=Did && dest<=id && Lid==0 ) || (dest>=Did && dest>=id && Rid==0 )){
    					    			//send to dad node
    					    		//	System.out.println("D");

    					    			if(Did==0){esc=true;}
    					    	}
    					    		if(esc==true){
    					    			sendACKtx(mitt, -10);
    					    	//		System.out.println("Nodo non trovato");
    					    		}
    					    		
    						
    						
    								byte[] data=new byte[1019];
    								if(n_fw==1)
    									System.out.println(mitt+" -> "+dest);
    								
    								n_fw=input.readByte();
    								
    								l1=input.readByte();
    								l2=input.readByte();
    								
    							
    								short dim= (short) ((l2<<8)+(l1&0xff));
    								//System.out.println("DIM: "+dim);

    								int letti=0;
    								dim=(short) (dim+1);
    								do{
    									int k=input.read(data, letti, dim-letti);
    									letti=letti+k;
    								}while(letti<dim);
    								//CHIAMO F

    								byte[] buf=new byte[5+dim];
    								buf[0]=dest;
    								buf[1]=mitt;
    								buf[2]=n_fw;
    								buf[3]=l1;
    								buf[4]=l2;
    								for(int i=5;i<(5+dim);i++){
    									buf[i]=data[i-5];
    								}
    					//			System.out.println((byte)data[dim-1]);
    								if((byte)data[dim-1]==-2 && !esc)
    									determineNextHop(dest, buf,n_fw);
    								else{
    									//System.out.println("PACKET DROPPED");
    								}
    								data=new byte[1019];
    								//System.out.println("FINE FORWARD.....................");
    							}
    							//System.out.println("DA: "+mitt+" TO "+dest+" N:"+n+ " N_FW:"+n_fw);
							}catch(java.io.EOFException e){/*System.out.println("Connessione chiusa dall'altro capo");*/done=true;}

    						}while(!done);
    				//	}
    				//	System.out.println("Connection closed.");
    				}
    				catch (InterruptedException e)
    				{
    					e.printStackTrace();
    				}
    				catch( IOException e)
    				{
    					e.printStackTrace();
    				}

    			}
    		}

    	}


}
